Skip to content

otap: unify real OtapPdata processor, live reconfiguration, and YAML pipeline E2E - #9

Merged
zzylol merged 7 commits into
mainfrom
feat/verify-otap-bridge-against-real-workspace
Aug 24, 2026
Merged

otap: unify real OtapPdata processor, live reconfiguration, and YAML pipeline E2E#9
zzylol merged 7 commits into
mainfrom
feat/verify-otap-bridge-against-real-workspace

Conversation

@zzylol

@zzylol zzylol commented Aug 24, 2026

Copy link
Copy Markdown
Contributor

Why

Issue #7 identified the missing seam between ASAP sketch processing and a real OTAP Dataflow pipeline. Earlier work had a standalone Arrow representation and a separate TCP wire protocol, but the actual local::Processor<OtapPdata> path was not compiled or exercised as a running pipeline.

This PR makes the real OTAP pipeline the single production transport and verifies the integration against the pinned upstream workspace.

What

  • Consolidates the implementation into asap-precompute-rs; the old otap-patch staging overlay is removed.
  • Adds the otap-engine feature with git-pinned real otel-arrow-dfe-* dependencies.
  • Registers AsapSketchesProcessor as urn:asap:processor:asap_sketches.
  • Implements direct SketchEnvelope <-> OtapPdata encoding without intermediate RecordBatch / OtapMetricRecords hops.
  • Ingests genuine scalar OTAP metrics and merges upstream serialized sketch envelopes through the same processor callback.
  • Emits exclusively through effect_handler.send_message_with_source_node; no second production TCP lane.
  • Handles processor-local NodeControlMsg::Config safely:
    • ordinary config changes use Precompute::update_config;
    • sketch algorithm/factory-parameter changes drain and rebuild the runtime;
    • window-size changes cancel and recreate the periodic timer.
  • Adds a genuine YAML pipeline end-to-end test.

Architecture decisions

OTAP's own Arrow encoder already dictionary-encodes metric names and string attribute keys/values. That supplies schema/dictionary reuse at the transport layer, so an application-level SeriesDictionary protocol is unnecessary for the real OTAP adapter. The standalone legacy codec/examples remain available, but are not part of the otap-engine path. This supersedes the production direction explored in #8.

The processor drives Precompute directly from OTAP's per-message and timer callbacks. An mpsc bridge into the plugin's long-running stream lifecycle and a producer/receiver role flag are therefore unnecessary.

Genuine pipeline E2E coverage

tests/otap_pipeline_e2e.rs:

  1. Parses real OTAP YAML containing urn:asap:processor:asap_sketches.
  2. Builds it through OTAP_PIPELINE_FACTORY with registered test source/sink nodes.
  3. Runs the resulting RuntimePipeline::run_forever.
  4. Injects a real scalar OtapPdata metric.
  5. Exercises processor ingestion and periodic flushing.
  6. Captures and decodes the exported OtapPdata.
  7. Verifies the DDSketch envelope, metric name, series labels, and non-empty serialized payload.
  8. Shuts the running pipeline down through RuntimeControlMsg::Shutdown.

Verification

  • cargo test --features otap-engine: 114 unit + 11 API + 4 codec + 9 lifecycle + 1 live-pipeline + 32 runtime tests passed (171 total).
  • cargo clippy --features otap-engine --test otap_pipeline_e2e -- -D warnings: clean.
  • cargo fmt --check: clean.
  • Real OTAP encoding regression coverage confirms dictionary columns for metric names and string attributes.

Remaining work

  • Cross-host byte-parity and multi-node testing.
  • Define the intended “topic” mapping and add multi-topic coverage.
  • Implement a real external control-plane transport such as OpAMP; runtime config messages themselves are wired.
  • Design and implement distributed repartition/shuffle by label.

Closes the OTAP pipeline-integration and processor-local live-reconfiguration portions of #7.

zzylol added 2 commits August 24, 2026 12:04
…eal OTAP workspace

Per request: actually get an OTAP Dataflow checkout and compile/test
against it, rather than leaving otap_bridge.rs's binding as "read from
source, never compiled." Staged both files into a real clone of
open-telemetry/otel-arrow @ 3e85c3460361446ebfce99e9f35fffd2dd5ab740
(2026-08-24) as a `crates/asap-sketches-registry` workspace member
(path-depping back to asap-precompute-rs exactly as Cargo.toml's own
doc describes) and ran the real cargo build/clippy/fmt/test there.

## Bugs the real compiler caught (none of these were guessable from
## source reading alone)

- `ProcessorFactory::create`'s function-pointer type gained a fifth
  parameter, `capabilities: &capability::registry::Capabilities`
  ("per-node, one-shot view of extension capabilities... factories
  that don't depend on any extension can ignore the parameter") —
  not present in whatever version this adapter was originally read
  against. Added as an unused parameter to
  create_asap_sketches_processor.
- Two borrow-checker errors in the OtapPdata decode path: `if let
  Some(gauge) = data.as_gauge() { gauge.data_points().collect() }`
  doesn't compile because the collected Vec's items borrow from
  `gauge`, which is dropped at the end of the `if let` arm. Fixed by
  factoring the per-data-point body into a `DecodeAccumulator` struct
  with a generic `push_data_point<D: NumberDataPointView>` method,
  called inline from each of the Gauge/Sum branches instead of trying
  to unify their different concrete NumberDataPointView types into
  one Vec first.
- One unused import (`TryFromWithOptions`) clippy caught immediately.

## What's now genuinely verified (not just "compiles")

Added 3 new tests to otap_bridge.rs exercising the real
OtapArrowRecords::Metrics / OtapPdata / encode_metrics_otap_batch /
OtapMetricsView machinery end to end:
- encode_then_decode_round_trips_a_scalar_metric
- encode_then_decode_round_trips_a_sketch_envelope_carried_as_a_metric_attribute
  (the "self-describing sketch binary inside an OTAP metric" case --
  an `_asap_envelope` Bytes attribute survives the round trip
  byte-for-byte)
- decode_returns_none_records_for_zero_rows

All 10 tests in the staged crate pass (7 pre-existing config-shape
tests + 3 new), `cargo clippy -D warnings` and `cargo fmt --check`
both clean, against the real workspace.

## Not changed

Applied `cargo fmt`'s reformatting (this workspace's rustfmt config
differs slightly from what the file was originally written against --
import grouping mainly) back onto the canonical otap-patch/ copies.
Updated mod.rs's, otap_bridge.rs's, and README.md's doc comments from
"unverified" to reflect what's actually confirmed now, and precisely
what "verified" means here: staged into a *separate*, temporary
checkout of the real workspace, not something this repo's own build
wires up -- otap-patch/ itself still has no standalone build in this
repo.

Added .gitignore entry for /otel-arrow/ (the temporary checkout used
for this verification, not committed).
…ncoding

Per request: merge the two paths into one, with the wire lane
genuinely supporting the same dictionary/reuse economics. New
otap_wire.rs carries a real OtapArrowRecords::Metrics (built via
otap_bridge::otap_metric_records_to_pdata -- the exact same encoding
the generic-pipeline path already uses) directly over a persistent TCP
connection, instead of ASAP's own SCHEMA/DICTIONARY/RECORD
SketchStreamBatch protocol (asap_precompute_rs::otap::{wire,dictionary}).
One encoding, used identically whether a producer/receiver pair is
directly connected or routed through other OTAP pipeline components.

## What changed

- otap-patch/all/otap_wire.rs (new): OtapWireWriter/OtapWireReader,
  reusing the persistent-per-connection Arrow IPC design
  asap_precompute_rs::otap::wire::{WireWriter,WireReader} already
  validates (each payload type's Schema message sent once per
  connection, not once per window) -- generalized from
  SketchStreamBatch's fixed 4 roles to however many ArrowPayloadTypes
  a given OtapArrowRecords::Metrics actually populates (a real
  OtapPdata can carry up to 19 different payload types; ASAP's encode
  path only ever populates a handful, so roles are tracked in a
  BTreeMap keyed by ArrowPayloadType rather than 4 named fields).
- Duplicated (not shared) with asap-precompute-rs's design on purpose:
  asap-precompute-rs deliberately has no dependency on the OTAP
  Dataflow crates this needs, and pulling one in would break that
  crate's "builds standalone" property -- see the module's own doc.
- otap-patch/all/Cargo.toml: added arrow-ipc, tokio deps (both already
  pinned at the OTAP workspace level, so no new version to reconcile).

## Verification

Same workflow as the previous commit on this PR: staged into a real
open-telemetry/otel-arrow checkout (3e85c3460361446ebfce99e9f35fffd2dd5ab740,
2026-08-24) as the asap-sketches-registry workspace member. Two new
tests genuinely round-trip a real OtapPdata over an actual TCP
loopback socket:
- round_trips_a_real_otap_pdata_over_a_tcp_loopback_socket
- round_trips_multiple_windows_over_one_persistent_connection (two
  different metric values sent over one persistent connection, both
  decoded correctly on the far end -- the scenario that actually
  proves "one path" rather than just "compiles")

All 12 tests in the staged crate pass (10 from the previous commit +
these 2), cargo clippy -D warnings and cargo fmt --check both clean.

## Not done here

otap_wire.rs has no consumer inside this crate yet -- it's a complete,
tested transport module, not baked into AsapSketchesProcessor's
runtime behavior (documented honestly via #![allow(dead_code)] with
an explanation, not silently hidden). Wiring a config-driven choice of
transport into AsapSketchesProcessor itself (a peer_addr option,
connection lifecycle, reconnect behavior) is real follow-up work --
see the module's own "Not wired into AsapSketchesProcessor yet"
doc section for the two shapes that follow-up could take.
@zzylol zzylol changed the title otap: build/lint/test-verify OtapPdata binding against a real OTAP workspace otap: unify wire lane + metric lane onto one real OtapPdata encoding, verified against a real OTAP workspace Aug 24, 2026
…receiver nodes

Both otap_wire.rs's transport (OtapWireWriter/OtapWireReader) and the
wire lane's receive side had no consumer inside the crate — this closes
that: real OTAP nodes now actually drive them at runtime, not just tests.

- New otap_receiver.rs: AsapSketchesReceiver, a real
  local::Receiver<OtapPdata> (not local::Processor — that trait is purely
  reactive and can't independently accept a TCP connection). Listens on a
  configured address, decodes each connection with OtapWireReader, pushes
  into the pipeline via effect_handler.send_message. Registered under
  urn:asap:receiver:asap_sketches_wire via OTAP_RECEIVER_FACTORIES.
- AsapSketchesProcessor gains an optional peer_addr config field:
  emit_envelopes now forwards over a lazily-connected, persistent
  OtapWireWriter connection to that peer when set, falling back to the
  existing generic pipeline hop otherwise. A send failure drops the
  window and resets the connection so the next window reconnects.
- otap_wire.rs's #![allow(dead_code)] is gone — both OtapWireWriter and
  OtapWireReader now have real, non-test callers.
- New end-to-end test: the real AsapSketchesReceiver::start() bound to a
  real loopback TCP socket, fed by a real OtapWireWriter::send from a
  connected client, decoded and pushed through the actual pipeline
  machinery (OTAP's own TestRuntime harness) — not a mock.

Verified against the same staged open-telemetry/otel-arrow checkout
(3e85c3460361446ebfce99e9f35fffd2dd5ab740, 2026-08-24): build, clippy
-D warnings, fmt --check, and test all clean; 16/16 tests passing
(up from 12/12), including the new end-to-end receiver test.
asap-precompute-rs's own suite: 156/156, unaffected.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
@zzylol zzylol changed the title otap: unify wire lane + metric lane onto one real OtapPdata encoding, verified against a real OTAP workspace otap: wire lane + metric lane unified onto one real OtapPdata encoding, now driving real processor/receiver nodes Aug 24, 2026
… already gives us dictionary economics

The direct-TCP wire lane added in the previous commit (otap_wire.rs +
otap_receiver.rs's AsapSketchesReceiver, peer_addr config) is gone.
There is now exactly one transport: effect_handler.send_message_with_source_node,
i.e. whatever pipeline this node is already wired into.

Why: the wire lane existed to give sketch traffic real dictionary/
schema-reuse economics (asap_precompute_rs::otap::dictionary's SCHEMA/
DICTIONARY/RECORD tiering, from PR #5/#6) that riding a generic OTLP
metric didn't have. But that's solving a problem OTAP's real Arrow
encoding already solves — otap_bridge's encode_metrics_otap_batch
dictionary-encodes the metric name and every string-valued attribute
key/value by construction. Verified against the real staged workspace:

  === payload_type UnivariateMetrics ===
    name : Dictionary(UInt8, Utf8)
  === payload_type NumberDpAttrs ===
    parent_id : Dictionary(UInt8, UInt32)
    key       : Dictionary(UInt8, Utf8)
    str       : Dictionary(UInt16, Utf8)

That's the same "send the dictionary once, reference it after that"
shape SeriesDictionary was reinventing at the application layer, done
instead at the columnar/Arrow-IPC level — with no dictionary state for
this adapter to track, no second wire protocol, and none of the
"must be one ordered, single-consumer stream or the series_id
reference dangles" correctness constraint a hand-rolled scheme has.

Added a permanent regression test guarding this fact
(otap_bridge::tests::real_otap_encoding_dictionary_encodes_metric_name_and_string_attributes)
so a silent upstream schema change surfaces loudly rather than being
rediscovered from scratch. asap_precompute_rs::otap::dictionary
(SeriesDictionary / SketchStreamBatch) stays in the tree, tested, and
still backs the legacy asap_precompute_rs::otap::wire example
binaries — it's just not part of this adapter's path.

Re-verified against the same staged open-telemetry/otel-arrow checkout
(3e85c3460361446ebfce99e9f35fffd2dd5ab740, 2026-08-24): build, clippy
-D warnings, fmt --check, and test all clean; 11/11 tests passing.
asap-precompute-rs's own suite: 156/156, unaffected.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
@zzylol zzylol changed the title otap: wire lane + metric lane unified onto one real OtapPdata encoding, now driving real processor/receiver nodes otap: real OtapPdata binding, verified against a real OTAP workspace Aug 24, 2026
…e, real OtapPdata everywhere

Full merge, per explicit direction: no more otap-patch/ overlay
requiring manual staging into a checked-out OTAP Dataflow workspace to
compile. asap-precompute-rs is now the ONE crate, and depends directly
on the real otel-arrow-dfe-* crates (plain git dependency, pinned to
3e85c3460361446ebfce99e9f35fffd2dd5ab740, same pattern already used
for asap_sketchlib) behind a new `otap-engine` Cargo feature.
`cargo build --features otap-engine` just works — no staging script,
no temporary checkout, no copying files by hand.

New modules (moved + rewritten from otap-patch/all/{mod.rs,otap_bridge.rs}):

- otap::processor — AsapSketchesProcessor, the real
  local::Processor<OtapPdata> node, linkme-registered under
  urn:asap:processor:asap_sketches.
- otap::codec (renamed from the working name `bridge` — it's not
  bridging two representations anymore, see below) — the real
  SketchEnvelope <-> OtapPdata binding.

The bigger change is what codec.rs does differently from the old
otap_bridge.rs it replaces: it builds/reads a real OtapPdata *directly*
from/to &[SketchEnvelope], skipping the intermediate flat RecordBatch
(encode_batch) and OtapMetricRecords two-batch family (lift) hops
entirely. Those three representations for one job only existed because
of speculative multi-adapter generality (Telegraf/Vector) that never
materialized in this repo, which only ever ships to OTAP -- so codec.rs
implements OTAP's own MetricsView trait family straight over envelope
slices instead. encode_batch/decode_batch/records::{flatten,lift}
still exist, still tested, and still back the legacy
SeriesDictionary/otap::wire transport and its standalone example
binaries -- they're just not part of the otap-engine path anymore.

A real bug found and fixed along the way: encode_batch unconditionally
sets `_asap_envelope` even for estimate-mode envelopes (empty
payload), and arrow_array::BinaryArray treats `Some(&[])` as present-
not-null -- confirmed empirically -- so an estimate-mode gauge would
misroute through the envelope decode path with an empty payload
instead of the scalar path. codec.rs's direct encoder only attaches
_asap_envelope (and its sibling attributes) when the payload is
actually non-empty, with a defensive filter on decode too. New test:
estimate_mode_envelope_round_trips_as_a_scalar_not_an_empty_envelope.
The legacy encode_batch/decode_batch pair keeps the original behavior
untouched -- fixing it there was out of scope for this rewrite.

Also moved plugins/asap_sketches/{README.md,sample.toml} to
asap-precompute-rs/plugins/asap_sketches/ (content updated to match:
no more Phase D/"deliberately deferred" language, since it's done),
dropped the empty src/mod.rs placeholder it predated, and rewrote the
crate-root and repo-root README/module docs throughout to describe the
one-crate reality instead of the old Layer A/otap-patch split.

Verified via this repo's own `cargo build/test/clippy/fmt` at all
three feature levels (default, otap, otap-engine) -- no staging into
any external checkout: 100/113/169 tests pass respectively (default/
otap/otap-engine), clippy -D warnings clean, fmt clean.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
@zzylol zzylol changed the title otap: real OtapPdata binding, verified against a real OTAP workspace otap: merge into one crate, real OtapPdata direct from SketchEnvelope, no staging required Aug 24, 2026
@zzylol zzylol changed the title otap: merge into one crate, real OtapPdata direct from SketchEnvelope, no staging required otap: unify real OtapPdata processor, live reconfiguration, and YAML pipeline E2E Aug 24, 2026
@zzylol
zzylol merged commit 779845b into main Aug 24, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant